﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace SqlBlocks
{
    public static class FileHelper
    {
        static string SqlNamePattern = @"((?<Group>[a-zA-Z][^_]*)_)?(?<Order>\d+\.?\d*)_(?<Name>.*)\.sql$";
        static string SqlDownNamePattern = @"((?<Group>[a-zA-Z][^_]*)_)?(?<Order>\d+\.?\d*)_(?<Name>.*)_down\.sql$";

        public static bool IsUp(string file)
        {
            return Regex.IsMatch(file, SqlNamePattern) && !IsDown(file);
        }

        public static bool IsDown(string file)
        {
            return Regex.IsMatch(file, SqlDownNamePattern);
        }

        public static double GetLastVersion(string path)
        {
            var migrations = GetMigrations(path);
            var v = 0.0;

            foreach (var m in migrations.Complete)
            {
                if (m.Version > v) v = m.Version;
            }
            return v;
        }

        /// <summary>
        /// Gets a list of .sql migrations at the specified path
        /// </summary>
        /// <param name="path">The path to search</param>
        /// <returns>An unsorted list of available migrations</returns>
        public static MigrationSet GetMigrations(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentNullException("path");
            }
            DirectoryInfo dir = null;
            if (Directory.Exists(path))
            {
                dir = new DirectoryInfo(path);
            }
            else if (File.Exists(path))
            {
                dir = new FileInfo(path).Directory;
            }

            if (dir == null)
            {
                throw new FileNotFoundException("The specified path was not found: " + path);
            }

            List<FileInfo> files = new List<FileInfo>(dir.GetFiles("*.sql", SearchOption.AllDirectories));

            string conflictingFile = null;
            if (ListHasDuplicateFileNames(files, out conflictingFile))
            {
                throw new ArgumentException(
                    "The specified path has multiple SQL files name \"" + conflictingFile + "\". " +
                    "SQL migrations must have unique filenames",
                    path
                    );
            }

            List<FileInfo> bad = new List<FileInfo>();

            //FilterValidMigrations(files, bad);
            var migrations = BuildFromSqlFileList(files);

            return migrations;
        }


        /// <summary>
        /// Builds a list of migrations from SQL file locations.  Incomplete migrations are added to the incompleteMigrations collection
        /// </summary>
        /// <param name="files">Files to process</param>
        /// <returns></returns>
        public static MigrationSet BuildFromSqlFileList(IList<FileInfo> files)
        {
            var set = new MigrationSet();

            for (int i = files.Count - 1; i >= 0; i--)
            {
                var file = files[i];

                if (!IsValidUpFile(file))
                {
                    files.RemoveAt(i);
                }
                else
                {
                    var downFileName = file.Name.Remove(file.Name.Length - 4) + "_down.sql";

                    var downFile = new FileInfo(Path.Combine(file.DirectoryName, downFileName));

                    var m = BuildFromSqlFile(file, downFile);

                    if (downFile.Exists)
                    {
                        set.Complete.Add(m);
                    }
                    else
                    {
                        set.Incomplete.Add(m);
                    }
                }
            }

            return set;
        }

        static bool IsValidUpFile(FileInfo file)
        {
            if (Regex.IsMatch(file.Name, SqlDownNamePattern))
            {
                // SQL Down files are treated a bit differently
                return false;
            }
            else if (!Regex.IsMatch(file.Name, SqlNamePattern))
            {
                // Not a valid file name, ignore it
                return false;
            }

            return true;
        }

        static MigrationDescription BuildFromSqlFile(FileInfo upFile, FileInfo downFile)
        {
            if (upFile == null)
            {
                throw new ArgumentNullException("upFile");
            }

            if (downFile == null)
            {
                throw new ArgumentNullException("downFile");
            }

            Regex regex = new Regex(
                SqlNamePattern,
                RegexOptions.IgnoreCase
                | RegexOptions.CultureInvariant
                | RegexOptions.IgnorePatternWhitespace
                | RegexOptions.ExplicitCapture
                );

            Match match = regex.Match(upFile.Name);
            if (match.Success)
            {
                double order = -1;
                double.TryParse(match.Groups["Order"].Value.Replace('_', '.'), out order);
                MigrationDescription desc = new MigrationDescription(
                    order,
                    match.Groups["Group"].Value,
                    match.Groups["Name"].Value,
                    new RawSqlMigration
                    {
                        UpFile = upFile.FullName,
                        DownFile = downFile.FullName
                    }
                );
                return desc;
            }
            return null;
        }

        static bool ListHasDuplicateFileNames(List<FileInfo> files, out string conflict)
        {
            var clean = new Dictionary<string, FileInfo>(files.Count, StringComparer.CurrentCultureIgnoreCase);
            foreach (var file in files)
            {
                if (clean.ContainsKey(file.Name))
                {
                    conflict = file.Name;
                    return true;
                }
                else
                {
                    clean.Add(file.Name, file);
                }
            }
            conflict = null;
            return false;
        }
    }
}
